﻿namespace Jsl.StyleCop.CSharp.RefactoringRules
{
    using System;
    using System.Globalization;
    using System.Linq;
    using Microsoft.StyleCop.CSharp;

    /// <summary>
    /// Finds variable being that could be set in their declaration.
    /// </summary>
    public class LocalVariablesShouldBeSetWhereTheyAreDeclared : CsRuleBase<RuleAnalyzer>
    {
        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="LocalVariablesShouldBeSetWhereTheyAreDeclared"/> class.
        /// </summary>
        /// <param name="analyzer">The analyzer.</param>
        /// <param name="document">The document.</param>
        public LocalVariablesShouldBeSetWhereTheyAreDeclared(RuleAnalyzer analyzer, CsDocument document)
            : base(analyzer)
        {
            this.Document = document;
        }
        #endregion Constructors

        #region Public Methods
        /// <summary>
        /// Analyzes the graph.
        /// </summary>
        /// <param name="element">The element to graph.</param>
        /// <param name="graph">The graph.</param>
        public override void AnalyzeGraph(CsElement element, CodeUnitGraph graph)
        {
            if (this.Analyzer.IsRuleEnabled(this.Document, RuleAnalyzer.LocalVariablesShouldBeSetWhereTheyAreDeclared))
            {
                this.CheckForAssigningInDeclarator(graph);
            }
        }
        #endregion Public Methods

        #region Private Methods
        private void CheckForAssigningInDeclarator(CodeUnitGraph graph)
        {
            // Process every variable declarator.
            foreach (VariableDeclaratorExpression variableDeclarator in graph.Vertices.OfType<VariableDeclaratorExpression>())
            {
                // Don't need to do anything if the variable is initialized in the declarator.
                if (variableDeclarator.Initializer == null)
                {
                    if (variableDeclarator.ParentStatement is VariableDeclarationStatement)
                    {
                        Variable variable = variableDeclarator.GetVariable();
                        LiteralExpression assignmentThatShouldBeMoved = null;
                        GraphSearchState result = CodeUnitGraphHelper.SearchChildCodePaths(
                            graph,
                            variableDeclarator.Identifier,
                            codeUnit =>
                            {
                                // If this is a LiteralExpression then it may be a usage of the variable.
                                LiteralExpression literalExpression = codeUnit as LiteralExpression;
                                if (literalExpression != null)
                                {
                                    var variableUsageInfo = literalExpression.GetVariableUsage();
                                    if (variableUsageInfo != null)
                                    {
                                        if (object.ReferenceEquals(variable, variableUsageInfo.Variable))
                                        {
                                            switch (variableUsageInfo.Usage)
                                            {
                                                case VariableUsage.Assignment:
                                                    if (object.ReferenceEquals(literalExpression.ParentStatement.Parent, variableDeclarator.ParentStatement.Parent))
                                                    {
                                                        assignmentThatShouldBeMoved = literalExpression;
                                                        return GraphSearchState.Found;
                                                    }
                                                    else
                                                    {
                                                        return GraphSearchState.NotFound;
                                                    }

                                                case VariableUsage.OutParameter:
                                                    // The variable was set in an out parameter so it can't be moved to the declarator.
                                                    return GraphSearchState.NotFound;

                                                default:
                                                    throw new InvalidOperationException(
                                                        string.Format(
                                                            CultureInfo.CurrentCulture,
                                                            "The variable '{0}' was used unexpectedly at position {1}.",
                                                            variable.Name,
                                                            literalExpression.Location.HumanReadable()));
                                            }
                                        }
                                    }
                                }

                                return GraphSearchState.ContinuePath;
                            });

                        if (result == GraphSearchState.Found)
                        {
                            this.AddViolation(
                                variableDeclarator.ParentElement(),
                                assignmentThatShouldBeMoved.LineNumber,
                                RuleAnalyzer.LocalVariablesShouldBeSetWhereTheyAreDeclared,
                                variableDeclarator.Identifier.Text);
                        }
                    }
                }
            }
        }
        #endregion Private Methods
    }
}